#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <linux/fb.h>

typedef struct {
	union {
		unsigned short int stype;                 /* Magic identifier            */
		unsigned char ctype[2];
	} type;
	unsigned int size;                       /* File size in bytes          */
	unsigned short int reserved1, reserved2;
	unsigned int offset;                     /* Offset to image data, bytes */
} __attribute__((packed)) bmp_file_header;



#define ASSERT_EQUAL(a,b) do {						\
		if ((a) != (b)) {					\
			printf("error: %s is not equal to %s\n ", #a,  #b); \
			exit(-1);					\
		} else {						\
			printf("OK: %s is equal to %s\n", #a, #b);	\
		}							\
	} while (0)


#define ASSERT_NOT_EQUAL(a,b) do {					\
		if ((a) == (b)) {					\
			printf("error: %s is equal to %s\n", #a, #b);	\
			exit(-1);					\
		} else {						\
			printf("OK: %s is not equal to %s\n", #a, #b);	\
		}							\
	} while (0)

#define PRINT_VALUE(a) do{\
  printf("%s = %d\n",#a,a);\
  }while(0)

typedef struct {
	unsigned int size;               /* Header size in bytes      */
	int width,height;                /* Width and height of image */
	unsigned short int planes;       /* Number of colour planes   */
	unsigned short int bits;         /* Bits per pixel            */
	unsigned int compression;        /* Compression type          */
	unsigned int imagesize;          /* Image size in bytes       */
	int xresolution,yresolution;     /* Pixels per meter          */
	unsigned int ncolours;           /* Number of colours         */
	unsigned int importantcolours;   /* Important colours         */
#if 0
  unsigned char blue;
  unsigned char green;
  unsigned char red;
  unsigned char reserved;
#endif
} __attribute__((packed)) bmp_info_header;




//static void print_memory(void * mem, int size);

#if 0
static void check_formats(struct fb_fix_screeninfo* p_fi, struct fb_var_screeninfo* p_vi)
{
	ASSERT_EQUAL(p_vi->bits_per_pixel, 32);
	ASSERT_EQUAL(p_fi->visual, FB_VISUAL_TRUECOLOR);
	ASSERT_EQUAL(p_fi->type, FB_TYPE_PACKED_PIXELS);	
	ASSERT_EQUAL(p_vi->xres%4, 0); // because BMP file format will require rounding up to 4 bytes, and we don't want do that shit

	ASSERT_EQUAL(p_vi->red.length, 8); 
	ASSERT_EQUAL(p_vi->red.msb_right, 0);

	ASSERT_EQUAL(p_vi->green.length, 8); 
	ASSERT_EQUAL(p_vi->green.msb_right, 0);

	ASSERT_EQUAL(p_vi->blue.length, 8); 
	ASSERT_EQUAL(p_vi->blue.msb_right, 0);
}
#endif

static void fb2bmp(unsigned char* fb_map_p, struct fb_fix_screeninfo* p_fi, struct fb_var_screeninfo* p_vi, const char *bmp_fname)
{
  //	check_formats(p_fi, p_vi);
	bmp_file_header file_header = {0};
	bmp_info_header info_header = {0};


	//mask
	int dwMask[3];
	dwMask[0] = 0x0000f800;
	dwMask[1] = 0x07e0;
	dwMask[2] = 0x1f;

	file_header.type.ctype[0] = 'B';
	file_header.type.ctype[1] = 'M';
	file_header.size = sizeof(file_header)+sizeof(info_header) + 2 * p_vi->xres * p_vi->yres;
	file_header.offset = sizeof(file_header)+sizeof(info_header) + sizeof(dwMask);

	info_header.size = sizeof(info_header);
	info_header.width = p_vi->xres;
	info_header.height = p_vi->yres;
	info_header.planes = 1;
	info_header.bits = 16;
	info_header.compression = 3;
	info_header.imagesize = 2 * p_vi->xres * p_vi->yres;
#if 0 	
	info_header.red = dwMask[0];
	info_header.green = dwMask[1];
	info_header.blue = dwMask[2];
#endif
	PRINT_VALUE(p_vi->bits_per_pixel);

	PRINT_VALUE(p_vi->blue.offset);
	PRINT_VALUE(p_vi->green.offset);
	PRINT_VALUE(p_vi->red.offset);

	PRINT_VALUE(p_fi->line_length);
	PRINT_VALUE(p_vi->yoffset);
	PRINT_VALUE(p_vi->xoffset);
	PRINT_VALUE(p_vi->xres);
	PRINT_VALUE(p_vi->yres);

	unsigned char* fb_start = fb_map_p + 
		p_fi->line_length * p_vi->yoffset +
		p_vi->xoffset * 2;

	FILE *fp = fopen(bmp_fname, "w");
	ASSERT_NOT_EQUAL(fp, NULL);
	fwrite(&file_header, sizeof(file_header), 1, fp);
	fwrite(&info_header, sizeof(info_header), 1, fp);
	fwrite(&dwMask,sizeof(dwMask),1,fp);
	fwrite(fb_start, info_header.imagesize, 1, fp);
	fclose(fp);	
}

#if 0
static void print_memory(void * mem, int size)
{
	int i;
	for (i=0; i < size; i++) {
		unsigned char *p = (unsigned char *)mem;
		printf ("%02x ", p[i]);
		if (i%16 == 15) {
			printf ("\n");
		} else if (i%8 == 7) {
			printf (" ");
		}
	}
	printf("\n\n");
}
#endif

int main(int argc, char *argv[])
{
    struct stat st;
    char buffer[1024*400];
    int size = 0;
	struct fb_var_screeninfo var_info;
	struct fb_fix_screeninfo fix_info;
    char tmp;
    int i = 0, j = 0;

    if(!stat(argv[1], &st)){
        printf("/home/chechunli/tmp/ophone_565.bin size is %ldk\n", st.st_size / 1024);
    }

	if (argc !=2 ) {
		printf("Usage: fb2bmp filename.bmp\n");
		printf("It can only work for 565 lcd \n");
		exit(-1);
	}
	
	int fd = open(argv[1], O_RDWR, 0);


#if 0
	if (fd < 0) { //try /dev/fb0
		fd = open("/dev/fb0", O_RDWR, 0);
	}

	if (fd < 0) {
		printf("error opening fb0\n");
		exit(-1);
	}
#endif
	
    var_info.xres = 320;
    var_info.yres = 480;
    var_info.bits_per_pixel = 16;

    var_info.red.length = 5;
    var_info.red.offset = 11;
    var_info.green.length = 6;
    var_info.green.offset = 5;
    var_info.blue.length = 5;
    var_info.blue.offset = 0;
    var_info.xoffset = 0;
    var_info.yoffset = 0;
    fix_info.line_length = var_info.xres * var_info.bits_per_pixel / 8 ;
    
    size = read(fd, buffer, st.st_size );   

#if 1
    /********* up to down **************/
    for ( i = 0; i < var_info.yres / 2 ; i++){
        for ( j = 0; j < var_info.xres * 2 ; j++){
            tmp = buffer[ i * var_info.xres *2 + j ];
            buffer[ i * var_info.xres * 2 + j ] = buffer[ (var_info.yres - i - 1) * var_info.xres * 2 + j] ;
            buffer[ (var_info.yres - i - 1) * var_info.xres * 2 + j] = tmp; 
        }
    }
#endif

    printf("size = %dk\n", size /1024); 

	fb2bmp((unsigned char *)buffer, &fix_info, &var_info, argv[1]); //argv[1] is the bmp output file name

	
	//we exit here, there's absolutely no need to do munmap/free/close/fclose.
	exit(0);
}
